
#include <stdio.h>
#include "raylib.h"

// enums
typedef enum GAME_STATE
{
    PLAYING,
    OVER,
    WIN,
} GAME_STATE;

typedef enum ENTITY
{
    GROUND,
    WALL,
    BALL,
    BASKET,
    ROLE,
} ENTITY;

// structs
typedef struct PosRowCol
{
    int row;
    int col;
} PosRowCol;

#define LEVELS_TOTAL 3
#define ROWS 10
#define COLS 10
#define CELL_WIDTH 48
#define CELL_HEIGHT 48

#define FRAME_TOTAL 7
#define FRAME_INTERVAL (1 / 6.f)

#define OVER_WAIT_TIME .5f

int levels[LEVELS_TOTAL][ROWS][COLS] = {
    {
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 1, 1, 1, 1, 1, 1, 1, 1, 0},
        {0, 1, 0, 0, 3, 0, 0, 0, 1, 0},
        {0, 1, 0, 0, 0, 0, 0, 0, 1, 0},
        {0, 1, 0, 0, 2, 2, 0, 3, 1, 0},
        {0, 1, 3, 2, 4, 0, 0, 0, 1, 0},
        {0, 1, 0, 0, 0, 2, 0, 0, 1, 0},
        {0, 1, 0, 0, 0, 3, 0, 0, 1, 0},
        {0, 1, 1, 1, 1, 1, 1, 1, 1, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    },
    {
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
        {0, 1, 0, 0, 1, 1, 0, 0, 1, 0},
        {1, 0, 3, 2, 0, 0, 2, 3, 0, 1},
        {1, 0, 0, 0, 4, 0, 0, 0, 0, 1},
        {0, 1, 0, 0, 2, 0, 0, 0, 1, 0},
        {0, 0, 1, 0, 0, 2, 0, 1, 0, 0},
        {0, 0, 0, 1, 3, 3, 1, 0, 0, 0},
        {0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    },
    {
        {0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
        {0, 0, 0, 1, 3, 0, 1, 0, 0, 0},
        {0, 0, 1, 0, 0, 0, 0, 1, 0, 0},
        {0, 1, 0, 2, 0, 0, 2, 0, 1, 0},
        {0, 1, 3, 0, 4, 0, 0, 3, 1, 0},
        {0, 1, 0, 2, 0, 2, 0, 0, 1, 0},
        {0, 0, 1, 0, 0, 0, 0, 1, 0, 0},
        {0, 0, 0, 1, 0, 3, 1, 0, 0, 0},
        {0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    }};

Texture textGround;
Texture textWall;
Texture textBasket;
Texture textBall;
Texture textGongji;
Texture textMuji;
Texture textBack;
Texture textRestart;
Texture textGege[FRAME_TOTAL];

Sound sndJi;
Sound sndGongji;
Sound sndWin;

GAME_STATE gameState;

int level = 1;
int layerStatic[ROWS][COLS];
int layerBall[ROWS][COLS];

PosRowCol rolePos;
int frameIdx = 0;
float frameTime = 0.0;

float overTime = 0.0f;

// functions
void init();
void initGame(int lvl);

void drawBg();
void drawStatic();
void drawBall();
void drawRole(float dt);
void driveOver(float dt);
void drawWin();
void checkOver();

void checkKeyDown();

Vector2 getPosOfRowCol(int row, int col);

bool showButton(Texture text, Vector2 pos);

int main()
{
    init();
    while (!WindowShouldClose())
    {
        checkKeyDown();

        BeginDrawing();
        drawBg();

        drawStatic();
        drawBall();
        drawRole(GetFrameTime());

        driveOver(GetFrameTime());

        drawWin();

        EndDrawing();
    }

    CloseWindow();
    return 0;
}

void init()
{
    InitWindow(600, 600, "Push Box");
    SetTargetFPS(60);

    textGround = LoadTexture("res/ground.png");
    textWall = LoadTexture("res/wall.png");
    textBasket = LoadTexture("res/basket.png");
    textBall = LoadTexture("res/ball.png");
    textGongji = LoadTexture("res/gongji.png");
    textMuji = LoadTexture("res/muji.png");
    textRestart = LoadTexture("res/restart.png");
    textBack = LoadTexture("res/back.png");
    for (int i = 0; i < FRAME_TOTAL; i++)
    {
        char str[20];
        sprintf(str, "res/gg%d.png", i);
        textGege[i] = LoadTexture(str);
    }

    InitAudioDevice();
    sndJi = LoadSound("res/sef_ji.mp3");
    sndGongji = LoadSound("res/sef_gongji.mp3");
    SetSoundVolume(sndGongji, 0.6f);
    sndWin = LoadSound("res/sef_win.mp3");

    initGame(1);
}

void initGame(int lvl)
{
    level = lvl;
    gameState = PLAYING;
    overTime = 0;

    for (int row = 0; row < ROWS; row++)
    {
        for (int col = 0; col < COLS; col++)
        {
            if (levels[level - 1][row][col] == WALL || levels[level - 1][row][col] == BASKET)
            {
                layerStatic[row][col] = levels[level - 1][row][col];
            }
            else
            {
                layerStatic[row][col] = 0;
            }

            if (levels[level - 1][row][col] == BALL)
            {
                layerBall[row][col] = 1;
            }
            else
            {
                layerBall[row][col] = 0;
            }

            if (levels[level - 1][row][col] == ROLE)
            {
                rolePos.row = row;
                rolePos.col = col;
            }
        }
    }
}

void checkKeyDown()
{
    if (IsKeyPressed(KEY_R))
    {
        initGame(level);
    }

    if (gameState != PLAYING)
    {
        return;
    }

    if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_W))
    {
        if (layerBall[rolePos.row - 1][rolePos.col] && layerStatic[rolePos.row - 2][rolePos.col] != WALL && !layerBall[rolePos.row - 2][rolePos.col])
        {
            // 上方是箱子 且上上方不是墙或者箱子 可以推过去 箱子和角色都向上
            layerBall[rolePos.row - 1][rolePos.col] = 0;
            layerBall[rolePos.row - 2][rolePos.col] = 1;
            if (layerStatic[rolePos.row - 2][rolePos.col] == BASKET)
            {
                // 推到目标上 播放音效
                PlaySound(sndJi);
            }
            rolePos.row--;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
        else if (layerStatic[rolePos.row - 1][rolePos.col] != WALL && !layerBall[rolePos.row - 1][rolePos.col])
        {
            // 上方不是墙也不是箱子 可以直接走
            rolePos.row--;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
    }
    else if (IsKeyPressed(KEY_DOWN) || IsKeyPressed(KEY_S))
    {
        if (layerBall[rolePos.row + 1][rolePos.col] && layerStatic[rolePos.row + 2][rolePos.col] != WALL && !layerBall[rolePos.row + 2][rolePos.col])
        {
            // 下方是箱子 且下方是空格 可以推过去 箱子和角色都向下
            layerBall[rolePos.row + 1][rolePos.col] = 0;
            layerBall[rolePos.row + 2][rolePos.col] = 1;
            if (layerStatic[rolePos.row + 2][rolePos.col] == BASKET)
            {
                PlaySound(sndJi);
            }
            rolePos.row++;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
        else if (layerStatic[rolePos.row + 1][rolePos.col] != WALL && !layerBall[rolePos.row + 1][rolePos.col])
        {
            // 下方不是墙也不是箱子 可以直接走
            rolePos.row++;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
    }
    else if (IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_A))
    {
        if (layerBall[rolePos.row][rolePos.col - 1] && layerStatic[rolePos.row][rolePos.col - 2] != WALL && !layerBall[rolePos.row][rolePos.col - 2])
        {
            // 左方是箱子 且左方是空格 可以推过去 箱子和角色都向左
            layerBall[rolePos.row][rolePos.col - 1] = 0;
            layerBall[rolePos.row][rolePos.col - 2] = 1;
            if (layerStatic[rolePos.row][rolePos.col - 2] == BASKET)
            {
                PlaySound(sndJi);
            }
            rolePos.col--;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
        else if (layerStatic[rolePos.row][rolePos.col - 1] != WALL && !layerBall[rolePos.row][rolePos.col - 1])
        {
            // 左方不是墙也不是箱子 可以直接走
            rolePos.col--;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
    }
    else if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_D))
    {
        if (layerBall[rolePos.row][rolePos.col + 1] && layerStatic[rolePos.row][rolePos.col + 2] != WALL && !layerBall[rolePos.row][rolePos.col + 2])
        {
            // 右方是箱子 且右方是空格 可以推过去 箱子和角色都向右
            layerBall[rolePos.row][rolePos.col + 1] = 0;
            layerBall[rolePos.row][rolePos.col + 2] = 1;
            if (layerStatic[rolePos.row][rolePos.col + 2] == BASKET)
            {
                PlaySound(sndJi);
            }
            rolePos.col++;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
        else if (layerStatic[rolePos.row][rolePos.col + 1] != WALL && !layerBall[rolePos.row][rolePos.col + 1])
        {
            // 右方不是墙也不是箱子 可以直接走
            rolePos.col++;
            if (layerStatic[rolePos.row][rolePos.col] == BASKET)
            {
                PlaySound(sndGongji);
            }
            checkOver();
        }
    }
}

void drawBg()
{
    ClearBackground((Color){231, 231, 231, 255});
}

Vector2 getPosOfRowCol(int row, int col)
{
    Vector2 startPos = (Vector2){46, 51};
    int x = startPos.x + col * CELL_WIDTH;
    int y = startPos.y + row * CELL_HEIGHT;
    return (Vector2){x, y};
}

void drawStatic()
{
    for (int row = 0; row < ROWS; row++)
    {
        for (int col = 0; col < COLS; col++)
        {
            Vector2 pos = getPosOfRowCol(row, col);

            int st = layerStatic[row][col];
            if (st == GROUND)
            {
                DrawTexture(textGround, pos.x, pos.y, WHITE);
            }
            else if (st == WALL)
            {
                DrawTexture(textWall, pos.x, pos.y, WHITE);
            }
            else if (st == BASKET)
            {
                // 如果上面有箱子或者角色 不显示
                if (layerBall[row][col] == 1 || (rolePos.row == row && rolePos.col == col))
                {
                    continue;
                }
                DrawTexture(textBasket, pos.x + 1.5, pos.y + 6, WHITE);
            }
        }
    }
}

void drawBall()
{
    for (int row = 0; row < ROWS; row++)
    {
        for (int col = 0; col < COLS; col++)
        {
            Vector2 pos = getPosOfRowCol(row, col);

            int st = layerBall[row][col];
            if (st == 1)
            {
                // 如果下面是目标点 显示母鸡
                if (layerStatic[row][col] == BASKET)
                {
                    DrawTexture(textMuji, pos.x + 4, pos.y + 4, WHITE);
                    continue;
                }

                DrawTexture(textBall, pos.x + 4, pos.y + 4, WHITE);
            }
        }
    }
}

void drawRole(float dt)
{
    frameTime += dt;
    if (frameTime >= FRAME_INTERVAL)
    {
        frameTime -= FRAME_INTERVAL;
        frameIdx = (frameIdx + 1) % FRAME_TOTAL;
    }
    Vector2 pos = getPosOfRowCol(rolePos.row, rolePos.col);

    // 如果下面是目标点 显示公鸡
    if (layerStatic[rolePos.row][rolePos.col] == BASKET)
    {
        DrawTexture(textGongji, pos.x + 4, pos.y + 4, WHITE);
        return;
    }

    pos.y -= 12;

    DrawTexturePro(textGege[frameIdx], (Rectangle){0, 0, textGege[frameIdx].width, textGege[frameIdx].height}, (Rectangle){pos.x, pos.y, 48, 60}, (Vector2){0, 0}, 0, WHITE);
}

void checkOver()
{
    // 所有的箱子都在目标点上
    bool allBoxInTarget = true;
    for (int row = 0; row < ROWS; row++)
    {
        for (int col = 0; col < COLS; col++)
        {
            if (layerBall[row][col] && layerStatic[row][col] != BASKET)
            {
                allBoxInTarget = false;
                goto over;
            }
        }
    }
over:
    if (allBoxInTarget)
    {
        gameState = OVER;
    }
}

void driveOver(float dt)
{
    if (gameState != OVER)
    {
        return;
    }

    overTime += dt;
    if (overTime >= OVER_WAIT_TIME)
    {
        overTime = 0;
        if (level < LEVELS_TOTAL)
        {
            initGame(level + 1);
        }
        else
        {
            gameState = WIN;
            PlaySound(sndWin);
        }
    }
}

void drawWin()
{
    if (gameState != WIN)
    {
        return;
    }

    DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), (Color){0, 0, 0, 128});

    if (showButton(textBack, (Vector2){100, 400}))
    {
        CloseWindow();
    }

    if (showButton(textRestart, (Vector2){400, 400}))
    {
        initGame(1);
    }
}

bool showButton(Texture text, Vector2 pos)
{
    Color color = WHITE;
    Rectangle rec = (Rectangle){pos.x, pos.y, text.width, text.height};
    Vector2 mousePoint = GetMousePosition();
    bool clicked = false;
    if (CheckCollisionPointRec(mousePoint, rec))
    {
        if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
        {
            color = (Color){127, 127, 127, 255};
        }
        else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
        {
            clicked = true;
        }
        else
        {
            color = (Color){200, 200, 200, 255};
        }
    }
    DrawTexture(text, pos.x, pos.y, color);
    return clicked;
}